package server;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import server.tets.MyClassLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.*;

/**
 * Minicat的主类
 */
public class MyBootstrap {
    // http://localhost:8080/web-demo02/resume/addresume02
    // http://localhost:8080/web-demo03/resume/addresume03
    private int port;

    /**
    * hostMap:封装host与context对应关系
    **/
    private Map<String, List<String>> hostContextMap = new HashMap<>();

    /**
     * hostMap:封装context与wrapper对应关系（一个项目只编写了一个拦截器所以用map，如果多个拦截，则同hostContextMap类似）
     **/
    private Map<String, String> contextWrapperMap = new HashMap<>();

    /**
     * hostMap:封装wrapper与className对应关系
     *   <servlet-mapping>
     *     <servlet-name>resumeServlet02</servlet-name>     <!--value-->
     *     <url-pattern>/resume/addresume02</url-pattern>   <!--key-->
     *   </servlet-mapping>
     **/
    private Map<String, String> wrapperClassNameMap = new HashMap<>();

    /**
     * 封装className与Class对应关系 (首字母小写)
     * resumeServlet02→ResumeServlet02（类）
     **/
    private Map<String, Object> classNameClassMap = new HashMap<>();


    public static void main(String[] args) {
        MyBootstrap myBootstrap = new MyBootstrap();
        try {
            myBootstrap.start();
            myBootstrap.println();
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Minicat启动需要初始化展开的一些操作
     */
    public void start() throws Exception {
        loadServer();
        println();

        // 定义一个线程池
        int corePoolSize = 10;
        int maximumPoolSize =50;
        long keepAliveTime = 100L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                handler
        );

        ServerSocket serverSocket = new ServerSocket(port);
        /*
            多线程改造（使用线程池）
         */
        while(true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor();
            requestProcessor.setSocket(socket);
            requestProcessor.setHostMap(hostContextMap);
            requestProcessor.setContextMap(contextWrapperMap);
            requestProcessor.setClassMap(classNameClassMap);
            requestProcessor.setWrapperMap(wrapperClassNameMap);
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    private void loadServer() {
        try {
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
            SAXReader saxReader = new SAXReader();
            Document read = saxReader.read(resourceAsStream);
            Element rootElement = read.getRootElement();
            List<Element> serviceList = rootElement.elements("Service");

            for (Element serviceElement : serviceList) {

                Element connector = serviceElement.element("Connector");
                String port = connector.attributeValue("port");
                this.port = Integer.parseInt(port);

                Element engine = (Element) serviceElement.elements("Engine").get(0);
                Element host = engine.element("Host");
                String hostName = host.attributeValue("name");

                String appBase = host.attributeValue("appBase");

                File file = new File(appBase);
                String context = file.getName();
                getClassNameClassMap(file);
                getWebXml(file, context);

                if (hostContextMap.get(hostName) == null || "".equals(hostContextMap.get(hostName))) {
                    List<String> contextList = new ArrayList<>();
                    contextList.add(context);
                    hostContextMap.put(hostName, contextList);
                } else {
                    hostContextMap.get(hostName).add(context);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initWebXml(File file, String context) {
        try {
            InputStream in = new FileInputStream(file);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(file.getAbsoluteFile());
            Element rootElement = document.getRootElement();

            Element servletMapping = rootElement.element("servlet-mapping");
            Element servletNameElement = (Element) servletMapping.element("servlet-name");
            String servletName = servletNameElement.getStringValue();

            Object o = classNameClassMap.get(servletName);
            if (o != null) {
                Element urlPatternElement = (Element) servletMapping.element("url-pattern");
                String urlPattern = urlPatternElement.getStringValue();
                contextWrapperMap.put(context, urlPattern);
                wrapperClassNameMap.put(urlPattern, servletName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void getWebXml(File file, String context) {
        if (file == null || !file.exists()) return;
        for (File listFile : file.listFiles()) {
            if (listFile.isFile()) {
                if (!"web.xml".equals(listFile.getName())) continue;
                initWebXml(listFile, context);
            } else {
                getWebXml(listFile, context);
            }
        }

    }

    private void getClassNameClassMap(File file) {
        if (file == null || !file.exists()) return;
        for (File listFile : file.listFiles()) {
            if (listFile.isFile()) {
                if (!listFile.getName().endsWith(".class")) continue;
                String name = listFile.getName().replace(".class", "");
                Object object = classLoader(listFile, name);
                classNameClassMap.put(lowerFirstCase(name), object);
            } else {
                getClassNameClassMap(listFile);
            }
        }
    }

    private Object classLoader (File file, String name) {
        try {
            //创建自定义类加载器实例
            ClassLoader cl = new MyClassLoader(file.getAbsolutePath());
            cl.getParent();
            try {
                Class clz = cl.loadClass(name);
                Constructor constructor = clz.getConstructor();
                Object obj = constructor.newInstance();
//                System.out.println(obj);
                return obj;
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private String lowerFirstCase(String str){
        char[] chars = str.toCharArray();
        //首字母小写方法，大写会变成小写，如果小写首字母会消失
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void println() {
        for (String host : hostContextMap.keySet()) {
            List<String> contextList = hostContextMap.get(host);
            System.out.print("hostContextMap：");
            for (String context : contextList) {
                System.out.print(context + ";");
            }
        }
        System.out.println("");
        for (String context : contextWrapperMap.keySet()) {
            System.out.println("contextWrapperMap：" + context + "→" + contextWrapperMap.get(context));
        }

        for (String wrapper : wrapperClassNameMap.keySet()) {
            System.out.println("wrapperClassNameMap：" + wrapper + "→" + wrapperClassNameMap.get(wrapper));
        }

        for (String className : classNameClassMap.keySet()) {
            System.out.println("classNameClassMap：" + className + "→" + classNameClassMap.get(className));
        }

    }

}
